/*
 * Copyright (C) 2008-2011 CBDP Consortium
 * 
 * All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.
 * 
 * This software consists of contributions made by many individuals, 
 * listed below:
 *
 * Author: 	Xabier Laiseca <xabier.laiseca@deusto.es>
 * 			David Ausín <david.ausin@deusto.es>
 */
package cbdp.server.contextreasoner.impl.contextmanager.online;

import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.Collection;
import java.util.Map;
import java.util.Vector;

import org.osgi.framework.BundleContext;

import cbdp.server.contextreasoner.Task;
import cbdp.server.contextreasoner.configuration.model.Configuration;
import cbdp.server.contextreasoner.exceptions.ContextManagerException;
import cbdp.server.contextreasoner.impl.ActionStatementListener;
import cbdp.server.contextreasoner.impl.contextmanager.online.model.EagerSavingInstance;
import cbdp.server.contextreasoner.impl.contextmanager.online.model.wrappers.EagerSavingInstanceRuleLaunchingWrapper;
import cbdp.server.contextreasoner.impl.reasoner.GenericReasoner;
import cbdp.server.contextreasoner.impl.util.NullChecker;
import cbdp.server.contextreasoner.internal.InternalContextManager;
import cbdp.server.contextreasoner.internal.OntologyContainer;
import cbdp.server.contextreasoner.model.Instance;
import cbdp.server.contextreasoner.model.exceptions.NotExistingInstanceException;

import com.hp.hpl.jena.ontology.Individual;
import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.rdf.listeners.StatementListener;
import com.hp.hpl.jena.shared.Command;
import com.hp.hpl.jena.shared.JenaException;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;

/**
 * @author  Xabier Laiseca (University of Deusto)
 */
public class JenaContextManager implements InternalContextManager
{	
	private final static String CAN_NOT_LOAD_ONTOLOGY_FILE = "The file can not be loaded as an ontology";
	private final static String NOT_EXISTING_CLASS = "The ontology class does not exist";
	
	private BundleContext bc;
	

	
	
	/**
	 * @uml.property  name="ontModel"
	 */
	//protected OntModel ontModel;
	private OntologyContainer ontologyContainer;
	
	protected GenericReasoner reasoner;
	
	/**
	 * @uml.property  name="ontologyURIs"
	 * @uml.associationEnd  
	 */
	private Configuration configuration;
	
	//This value can be changed by a subclass
	protected OntModelSpec ontModelSpec = OntModelSpec.OWL_MEM_MICRO_RULE_INF; 
	
	private StatementListener actionStatementListener;
	
	public JenaContextManager(final BundleContext bc)
	{
		this.bc = bc;
	}
	
	public void setConfiguration(final Configuration configuration)
	{
		this.configuration = configuration;
	}
	
	public void setOntologyContainer(final OntologyContainer ontologyContainer)
	{
		this.ontologyContainer = ontologyContainer;
	}
	
	protected OntModel getOntModel()
	{
		return ontologyContainer.getOntModel();
	}
	
	public void load()
	{		
		configureLogger();
		
		loadOntologies();
		
		addListener();
	}
	
	@Override
	public void unload() { }
	
	private final static String BUNDLE_PREFFIX = "bundle:";
	private final static String SUFFIX = "#";
	
	private void loadOntologies()
	{
		//final UrlValidator urlValidator = new UrlValidator();
		
		for (final String ontologyName : configuration.getOntologyLocations().keySet())
		{
			try
			{
				final String location = configuration.getOntologyLocations().get(ontologyName);
				
				if (!isOntologyAlreadyLoaded(location))
				{
					String ontologyLocation = getOntModel().getDocumentManager().getFileManager().mapURI(location);
					
					if (ontologyLocation.startsWith(BUNDLE_PREFFIX))
					{
						ontologyLocation = ontologyLocation.substring(BUNDLE_PREFFIX.length());
					}
					
					if (ontologyLocation.endsWith(SUFFIX))
					{
						ontologyLocation = ontologyLocation.substring(0, ontologyLocation.length() - SUFFIX.length());
					}
					
					/*if (!urlValidator.isValid(ontologyLocation))
					{
						ontologyLocation = JenaContextManager.class.getResource(ontologyLocation).toString();
					}*/
					if (isValid(ontologyLocation))
					{
						final URI uri = URI.create(ontologyLocation);
						
						if(!uri.isAbsolute())
						{
							ontologyLocation = JenaContextManager.class.getResource(ontologyLocation).toString();
						}
					}
					
					this.getOntModel().read(ontologyLocation);
				}
			}
			catch (Exception e)
			{
				e.printStackTrace();
			}
		}
	}
	
	private boolean isValid(final String uri)
	{
		try
		{
			URI.create(uri);
			return true;	
		}
		catch(final Exception e)
		{
			return false;
		}
	}
	
	private boolean isOntologyAlreadyLoaded(final String location)
	{
		return (getOntModel().getOntology(location) != null);
	}

	private void addListener()
	{
		if(bc != null)
		{
			actionStatementListener = new ActionStatementListener(this.bc, this, ontologyContainer);
			getOntModel().register(actionStatementListener);
		}
	}
	
	protected void configureLogger()
	{
		System.setProperty("java.util.logging.config.file", "");
	}
	
	protected void checkURI(final String uri)
	{
		try
		{
			URI.create(uri).toURL();
		}
		catch (MalformedURLException e)
		{
			throw new IllegalArgumentException(e);
		}
	}
	
	/* **************** methods of the interface ContextManager **************** */
	
	@Override
	@Deprecated
	public void extendOntology(final InputStream ontology, final String namespace) throws ContextManagerException
	{
		
		String name = namespace.substring(namespace.lastIndexOf("/"), namespace.length());
		
		if (name.endsWith("#"))
		{
			name = name.substring(0, name.length() -1);
		}
		
		extendOntology(ontology, namespace, name);
	}
	
	@Override
	public void extendOntology(final InputStream ontology, final String namespace, final String ontologyName) throws ContextManagerException
	{
		NullChecker.checkNullParameter(ontology, "ontology");
		NullChecker.checkNullParameter(namespace, "namespace");
		NullChecker.checkNullParameter(ontologyName, "ontologyName");
		
		if (namespace != null)
		{
			checkURI(namespace);
		}
		
		try
		{
			if (!isOntologyAlreadyLoaded(namespace))
			{
				getOntModel().read(ontology, namespace);
			}
			
			configuration.addOntologyLocation(ontologyName, namespace);
		}
		catch (final JenaException e)
		{
			throw new ContextManagerException(CAN_NOT_LOAD_ONTOLOGY_FILE, e);
		}
	}

	private Instance createInstance(final Individual individual)
	{
		final EagerSavingInstance instance = new EagerSavingInstance();
		instance.setOntModel(getOntModel());
		instance.setIndividual(individual);
		
		final EagerSavingInstanceRuleLaunchingWrapper wrapper = new EagerSavingInstanceRuleLaunchingWrapper();
		wrapper.setInstance(instance);
		wrapper.setInternalContextManager(this);
		
		return wrapper;
	}
	
	protected Individual getIndividual(final String id) throws NotExistingInstanceException
	{
		final Individual individual = getOntModel().getIndividual(id);
		
		if (individual == null)
		{
			throw new NotExistingInstanceException("The instance " + id + " does not exist");
		}
		
		return individual;
	}
	
	@Override
	public void createInstance(final String id, final String type) throws ContextManagerException
	{
		NullChecker.checkNullParameter(id, "id");
		NullChecker.checkNullParameter(type, "type");
		
		checkURI(id);
		checkURI(type);
		
		final OntClass ontClass = getOntModel().getOntClass(type);
		NullChecker.checkNullResource(ontClass, NOT_EXISTING_CLASS);
		
		ontClass.createIndividual(id);
	}
	
	@Override
	public Instance getInstance(final String id) throws ContextManagerException, NotExistingInstanceException
	{
		NullChecker.checkNullParameter(id, "id");
		
		checkURI(id);
		
		final Individual individual = getIndividual(id);
		
		return createInstance(individual);
	}
	
	@Override
	public Instance getInstance(final String id, final String type) throws ContextManagerException
	{
		try
		{
			return getInstance(id);
		}
		catch (NotExistingInstanceException e)
		{
			NullChecker.checkNullParameter(id, "id");
			checkURI(id);
			
			NullChecker.checkNullParameter(type, "type");
			checkURI(type);
			
			createInstance(id, type);
			
			try
			{
				return getInstance(id);
			}
			catch (NotExistingInstanceException e1)
			{
				throw new ContextManagerException("The instance should exist!", e1);
			}
		}
	}
	
	@Override
	public Collection<Instance> getInstances(final String clazz) throws ContextManagerException
	{
		NullChecker.checkNullParameter(clazz, "clazz");
		
		checkURI(clazz);
		
		final Vector<Instance> instances = new Vector<Instance>();
		
		final OntClass ontClazz = getOntModel().getOntClass(clazz);
		NullChecker.checkNullResource(ontClazz, NOT_EXISTING_CLASS);
		
		final ExtendedIterator<Individual> individuals = getOntModel().listIndividuals(ontClazz);
		
		while (individuals.hasNext())
		{
			final Individual individual = individuals.next();
			
			if (!individual.isAnon())
			{
				final Instance instance = createInstance(individual);
				instances.add(instance);
			}
		}
		
		return instances;
	}

	@Override
	public void deleteInstance(final String id) throws ContextManagerException, NotExistingInstanceException
	{
		NullChecker.checkNullParameter(id, "id");
		
		checkURI(id);
		
		final Individual individual = getIndividual(id);
		
		individual.remove();
	}


	/**
     * @return
     * @uml.property  name="ontologyURIs"
     */
	@Override
	public Map<String, String> getOntologyURIs()
	{
		return configuration.getOntologyLocations();
	}
	
	/* ******************** methods for execution support ************************* */
	
	private volatile boolean inExecution = false;
	private Object lock = new Object();
	
	@Override
	public boolean isInExecution()
	{
		return inExecution;
	}
	
	@Override
	public void execute(final Task task)
	{
		synchronized (lock)
		{
			try
			{
				inExecution = true;
				
				if(getOntModel().supportsTransactions())
				{
					getOntModel().executeInTransaction(new InternalCommand(task));
				}
				else
				{
					task.execute();
				}
			}
			finally
			{
				inExecution = false;
			}			
		}
		
	}
	
	private class InternalCommand implements Command
	{
		private final Task task;
		
		private InternalCommand(final Task task)
		{
			this.task = task;
		}

		@Override
		public Object execute()
		{
			task.execute();
			
			return null;
		}
		
	}
	
	
	/* ********************** Other public methods ************************** */
	
	public void setReasoner(final GenericReasoner reasoner)
	{
		this.reasoner = reasoner;
	}

	@Override
	public GenericReasoner getGenericReasoner()
	{
		return reasoner;
	}
}